import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import CMD from 'src/cmd';
import dev from 'src/config/dev';
import { ChargeStrategy } from 'src/entities/ChargeStrategy';
import { Device } from 'src/entities/Device';
import { DevicePort } from 'src/entities/DevicePort';
import { Merchant } from 'src/entities/Merchant';
import { ChargeStrategyModelService } from 'src/model/charge-strategy-model/charge-strategy-model.service';
import { DeviceModelService } from 'src/model/device-model/device-model.service';
import { IotSimModelService } from 'src/model/iot-sim-model/iot-sim-model.service';
import { MerchantModelService } from 'src/model/merchant-model/merchant-model.service';
import { MqttService } from 'src/mqtt/mqtt.service';
import { RedisService } from 'src/redis/redis.service';
import { DeviceResult } from 'src/type/Device.result';
import { getTime, isMerchant, isPlatform, isSubMerchant } from 'src/utils';
import { Connection, FindConditions, In, Like } from 'typeorm';
import { DistrictService } from '../district/district.service';
import ListDto, { AddDto, EditDto } from './dto';

@Injectable()
export class DeviceService {
    private devicePortKey:string; //设备端口在redis里面的key
    constructor(
        private model:DeviceModelService,
        private iotSimModelService:IotSimModelService,
        private merchantModelService:MerchantModelService,
        private districtService:DistrictService,
        private connection:Connection,
        private chargeStrategyModelService:ChargeStrategyModelService,
        private mqtt:MqttService,
        private configService:ConfigService,
        private redisService:RedisService,
    ) {
        this.devicePortKey = this.configService.get('redisKey.devicePort')
    }

    async list(dto:ListDto, roleType:RoleType, userId:number) {
        let where:FindConditions<Device> = {}
        let fields:(keyof Device)[] = ['deviceId', 'deviceName', 'portCount', 'strategyId', 'connectStatus', 'onlineTime', 'offlineTime']

        if(dto.deviceId) {
            where.deviceId = dto.deviceId
        }

        if(isPlatform(roleType)) {
            if(dto.merchantId) {
                where.merchantId = dto.merchantId
            }
            fields.push('merchantId', 'subMerchantId')
        }else if(isMerchant(roleType)) {
            where.merchantId = userId
            fields.push('subMerchantId')
        }else if(isSubMerchant(roleType)) {
            where.subMerchantId = userId
        }

        if(isPlatform(roleType) || isMerchant(roleType)) {
            if(dto.subMerchantId) {
                where.merchantId = dto.subMerchantId
            }
        }

        if(dto.cate_1) {
            where.cate_1 = dto.cate_1
        }

        if(dto.cate_2) {
            where.cate_2 = dto.cate_2
        }

        if(dto.deviceName) {
            where.deviceName = Like(`%${dto.deviceName}%`)
        }
        
        let result:PaginationResult<DeviceResult> = {
            total : 0,
            rows : []
        }
        result.total = await this.model.count(where)
        if(result.total > 0) {
            const rows:DeviceResult[] = await this.model.pagination(dto.pageNo, dto.pageSize, where, fields, {deviceId : 'DESC'})
            result.rows = rows

            const strategyIds = new Set()
            const merchantIds = new Set()

            rows.forEach(row => {
                if(!isSubMerchant(roleType)) {
                    if(isPlatform(roleType) && row.merchantId > 0) merchantIds.add(row.merchantId)
                    if(row.subMerchantId > 0) merchantIds.add(row.subMerchantId)
                }

                if(row.strategyId > 0) {
                    strategyIds.add(row.strategyId)
                }
            })

            let strategys:ChargeStrategy[] = []
            let merchants:Merchant[] = []

            if(strategyIds.size > 0) {
                strategys = await this.chargeStrategyModelService.find({strategyId : In(Array.from(strategyIds))},['strategyId', 'strategyName'])
            }

            if(merchantIds.size > 0) {
                merchants = await this.merchantModelService.find({merchantId : In(Array.from(merchantIds))})
            }

            if(strategys.length > 0 || merchants.length > 0) {
                rows.forEach(row => {
                    for(let i = 0; i < strategys.length; i ++) {
                        if(row.strategyId == strategys[i].strategyId) {
                            row.strategyName = strategys[i].strategyName
                            break
                        }
                    }

                    for(let i = 0; i < merchants.length; i ++) {
                        const id = merchants[i].merchantId
                        if(row.merchantId == id) {
                            row.merchantName = merchants[i].merchantName
                            break
                        }
                        if(row.subMerchantId == id) {
                            row.subMerchantName = merchants[i].merchantName
                            break
                        }
                    }
                })
            }

            
            

            // if(isPlatform(roleType)) {
            //     let districtIds:number[] = []

            //     result.rows.forEach(item => {
            //         if(item.provinceId > 0 && !districtIds.includes(item.provinceId)) {
            //             districtIds.push(item.provinceId)
            //         }
            //         if(item.cityId > 0 && !districtIds.includes(item.cityId)) {
            //             districtIds.push(item.cityId)
            //         }
            //         if(item.districtId > 0 && !districtIds.includes(item.districtId)) {
            //             districtIds.push(item.districtId)
            //         }
            //     })

            //     if(districtIds.length > 0) {
            //         const districtMap = await this.districtService.getDistrictMapByIds(districtIds)
            //         result.rows.forEach(item => {
            //             item.province = districtMap[item.provinceId]
            //             item.city = districtMap[item.cityId]
            //             item.district = districtMap[item.districtId]
            //         })
            //     }
            // }

            

            

        }

        return result
    }

    simIsUsed(simId:number) {
        return this.model.isExistsByWhere({simId : simId})
    }

    async add(dto:AddDto) {
        let data:{[key in keyof Device]? : Device[key]} = {portCount : dto.portCount, ...dto}
        if(dto.simId) {
            if((await this.simIsUsed(dto.simId)) == true) {
                throw new Error('SIM卡已被占用')
            }

            const sim = await this.iotSimModelService.findOne({id : dto.simId}, ['cardNo'])
            if(!sim) throw new Error('物联网卡不存在')
            data.simCardNo = sim.cardNo
            data.simId = dto.simId
        }

        // if(dto.merchantId) {
        //     data.merchantId = dto.merchantId
        // }

        // if(dto.deviceName) {
        //     data.deviceName = dto.deviceName
        // }

        // if(dto.provinceId) {
        //     data.provinceId = dto.provinceId
        // }

        // if(dto.cityId) {
        //     data.cityId = dto.cityId
        // }

        // if(dto.districtId) {
        //     data.districtId = dto.districtId
        // }

        // if(dto.address) {
        //     data.address = dto.address
        // }

        // if(dto.remark) {
        //     data.remark = dto.remark
        // }

        data.createTime = getTime()

        const queryRunner = this.connection.createQueryRunner();
        await queryRunner.connect();
        await queryRunner.startTransaction()

        try {
            
            const { raw } = await queryRunner.manager.insert<Device>(Device, data)
            if(!raw || raw.affectedRows < 1) {
                queryRunner.rollbackTransaction()
                return false
            }

            const deviceId = raw.insertId

            let portData:{[key in keyof DevicePort]? : DevicePort[key]}[] = []
            let devicePortHash = []
            for(let i = 1; i <= data.portCount; i ++) {
                portData.push({deviceId : deviceId, portNo : i})
                devicePortHash.push(String(i), '0')
            }

            {
                const { raw } = await queryRunner.manager.insert<DevicePort>(DevicePort, portData)
                if(!raw || raw.affectedRows < 1) {
                    queryRunner.rollbackTransaction()
                    return false
                }
            }

            //在redis里构建端口哈希
            const devicePortKey = this.devicePortKey + deviceId
            this.redisService.store.hSet(devicePortKey, devicePortHash)

            

            await queryRunner.commitTransaction()

            return true


        } catch (error) {
            await queryRunner.rollbackTransaction()
        } finally {
            await queryRunner.release()
        }

        return false
    }

    async getEditData(deviceId:number, type) {
        let fields:(keyof Device)[] = ['cate_1', 'cate_2', 'deviceName', 'provinceId', 'cityId', 'districtId', 'longitude', 'latitude', 'address', 'remark']
        if(isPlatform(type)) {
            fields.push('portCount', 'simId', 'simCardNo', 'merchantId')
        }
        
        const device:DeviceResult = await this.model.findOne({deviceId}, fields)

        if(device && device.merchantId) {
            device.merchantName = await this.merchantModelService.value({merchantId : device.merchantId}, 'merchantName') as string
        }

        return device
    }

    async edit(deviceId:number, dto:EditDto) {
        let data:{[key in keyof Device]? : Device[key]} = {}
        let fields = Object.keys(dto) as (keyof Device)[]
        if(fields.length == 0) return true
 
        const device = await this.model.findOne({deviceId : deviceId}, fields)

        if(!device) return true

        for(let k in device) {
            if(JSON.stringify(device[k]) != JSON.stringify(dto[k])) {
                data[k] = dto[k]
            }
        }

        if(Object.keys(data).length == 0) return true

        if(data.simId) {
            if((await this.simIsUsed(data.simId)) == true) {
                throw new Error('SIM卡已被占用')
            }

            const sim = await this.iotSimModelService.findOne({id : data.simId}, ['cardNo'])
            if(!sim) throw new Error('物联网卡不存在')
            data.simCardNo = sim.cardNo
        }else if(data.simId === 0) {
            data.simCardNo = ''
        }

        data.updateTime = getTime()

        if(data.portCount) { //修改端口
            const queryRunner = this.connection.createQueryRunner();
            await queryRunner.connect();
            await queryRunner.startTransaction()

            try {
                const { raw } = await queryRunner.manager.update<Device>(Device, {deviceId : deviceId}, data)
                if(!raw || raw.affectedRows < 1) {
                    queryRunner.rollbackTransaction()
                    return false
                }

                if(data.portCount > device.portCount) { //增加
                    let portData:{[key in keyof DevicePort]? : DevicePort[key]}[] = []
                    for(let i = device.portCount + 1; i <= data.portCount; i ++) {
                        portData.push({deviceId : deviceId, portNo : i})
                    }

                    const { raw } = await queryRunner.manager.insert<DevicePort>(DevicePort, portData)
                    if(!raw || raw.affectedRows < 1) {
                        queryRunner.rollbackTransaction()
                        return false
                    }

                }else { //减少
                    let portNos:number[] = []
                    for(let i = device.portCount; i > data.portCount; i --) {
                        portNos.push(i)
                    }


                    const { raw } = await queryRunner.manager.delete<DevicePort>(DevicePort, {deviceId : deviceId, portNo : In(portNos)})
                    if(!raw || raw.affectedRows < 1) {
                        queryRunner.rollbackTransaction()
                        return false
                    }

                }

                await queryRunner.commitTransaction()

                return true

            } catch (error) {
                await queryRunner.rollbackTransaction()
            } finally {
                await queryRunner.release()
            }

        }else {
            try {
                let affectedRows = await this.model.update({deviceId : deviceId}, data)
                if(affectedRows) {
                    return true
                }
            } catch (error) {
                
            }
        }

        return false

    }

    async allotMerchant(merchantId:number, deviceIds:number[]) {
        const devices = await this.model.find({deviceId : In(deviceIds)}, ['deviceId', 'merchantId'])
        deviceIds.splice(0)
        if(devices.length == 0) return false
        const _deviceIds:number[] = []
        devices.forEach(device => {
            if(device.merchantId != merchantId) {
                deviceIds.push(device.deviceId)
            }
        })

        if(deviceIds.length > 0) {
            const res = await this.model.update(deviceIds, {merchantId})
            if(!res) return false
        }

        return true
    }

    async setStrategy(strategyId:number, deviceIds:number[]) {
        const devices = await this.model.find({deviceId : In(deviceIds)}, ['simCardNo'])
        const simCardNos:string[] = []
        devices.forEach(dev => {
            if(dev.simCardNo) {
                simCardNos.push(dev.simCardNo)
            }
        })

        if(simCardNos.length > 0) {
            const strategy = await this.chargeStrategyModelService.findOne({strategyId : strategyId}, ['integrated'])
            if(!strategy || !strategy.integrated) return false
            this.publishStrategyToDevice(JSON.stringify(strategy.integrated), simCardNos)
            this.model.update(deviceIds, {strategyId})
            return true
        }else {
            return false
        }

    }

    async publishStrategyToDevice(strategy:any, simCardNos:string[]) {
        const topic = this.configService.get('mqtt.topic.chargeStarategy')
        const mqtt = await this.mqtt.connect()

        strategy.packType = 0
        strategy.cmdType = CMD.CMD_CARD_SETTING
        strategy = JSON.stringify(strategy)

        for(let i = 0; i < simCardNos.length; i ++) {
            mqtt.publish(topic + '/' + simCardNos[i], strategy, {qos : 2}, () => {
                if(i == (simCardNos.length - 1)) {
                    setTimeout(() => {
                        mqtt.end()
                    }, 2000)
                }
            })
        }
    }


}
